package com.atguigu.gulimall.product.web;

import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryService;
import com.atguigu.gulimall.product.vo.Catelog2Vo;
import org.redisson.api.RCountDownLatch;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author centurion
 * @description: TODO
 * @date 2024/9/30
 */
@Controller
public class IndexController {

    @Autowired
    private CategoryService categoryService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping({"/", "/index.html"})
    public String index(Model model) {
        List<CategoryEntity> categoryEntityList = this.categoryService.getLevel1CategoryList();

        model.addAttribute("categorys", categoryEntityList);
        //视图解析器会根据返回值和thymeleaf的前后缀进行拼接
        // classpath:/templates + 返回值 + .html
        return "index"; // 字符串时，返回的是视图的地址
    }

    @ResponseBody // 将方法返回的数据写入到响应体中
    @GetMapping({"/index/catalog.json"})
    public Map<String, List<Catelog2Vo>> getCatalogjson() {
        // Map<String, List<Catelog2Vo>> json = this.categoryService.getCatalogjson();

        //本地锁
        // Map<String, List<Catelog2Vo>> json = this.categoryService.getCatalogjson1();

        //redis实现的分布式锁
        // Map<String, List<Catelog2Vo>> json = this.categoryService.getCatalogjson2();

        //redisson实现分布式锁
        // Map<String, List<Catelog2Vo>> json = this.categoryService.getCatalogjson3();

        // spring-cache缓存
        Map<String, List<Catelog2Vo>> json = this.categoryService.getCatalogjson4();

        return json;
    }

    /**
     * redissonClient 实现分布式锁
     * @return
     */
    @ResponseBody // 将方法返回的数据写入到响应体中
    @GetMapping({"/index/test1"})
    public String testRedisson1() {
        //获取锁
        RLock lock = redissonClient.getLock("my-lock");
        //加锁,阻塞时等待,默认30s过期
        //1.锁会自动续期,如果业务处理时间过长（超过默认的看门口30s）,锁会自动续期
        //2.加锁的业务只要运行结束,就不会给锁进行续期
        //3.加锁后,服务宕机,默认30s过期
        /**
         * lock.lock(); 看门口默认时间30s
         * 服务正常运行，业务处理时间小于30s,不会续期
         * 服务正常运行，业务处理时间大于30s,会续期
         * 服务正常运行，加锁后，在30s内宕机，锁会在剩余时间后自动过期
         * 服务正常运行，加锁后，在30s外宕机，锁会在续期后的剩余时间自动过期
         *
         * 可重入锁，阻塞式，非公平锁（所有线程一起争抢释放的锁）
         */
        // lock.lock();
        /**
         * 30秒后自动解锁，这里锁的自动解锁时间一定要大于业务处理时间，否则会解锁失败（redis先自动解锁，java代码解锁会报错）
         * （例：java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: d7fe1dfd-2526-4753-8509-093e34a4e3e7 thread-id: 262）
         * 不会自动续期
         */
        lock.lock(30, TimeUnit.SECONDS);
        /**
         * 最佳实践 推荐使用  lock.lock(30, TimeUnit.SECONDS);
         * 显示指定锁的超时时间，省掉整个续期操作，手动解锁
         */
        System.out.println("加锁成功.........." + Thread.currentThread().getId());
        try {
            System.out.println("执行业务.........." + Thread.currentThread().getId());
            Thread.sleep(4000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            System.out.println("解锁成功.........." + Thread.currentThread().getId());
        }
        return "test1";
    }

    /**
     * 读写锁一般成对出现，保证读到最新数据,写锁是一个排他锁(互斥锁),读锁是一个共享锁
     * 写锁没释放,读就必须要等待
     *
     * 读 + 读:相当于无锁,并发读时,只会在redis中记录,当前的读锁所有线程都会同时加锁成功
     *     todo 实际测试发现是:读锁释放后,下一个读线程逐个获取释放读锁
     * 写 + 读:等待写锁释放,多个读线程同时获取读锁
     * 写 + 写:阻塞方式
     * 读 + 写:有读锁,写也要等待
     */
    /**
     * 分布式读写锁
     * @return
     */
    @ResponseBody // 将方法返回的数据写入到响应体中
    @GetMapping({"/index/writeValue"})
    public String testRedissonWriteLock() {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
        RLock writeLock = readWriteLock.writeLock();
        //加写锁
        writeLock.lock();
        System.out.println("加写锁 = " + Thread.currentThread().getId());
        String uuid = UUID.randomUUID().toString();
        try {
            redisTemplate.opsForValue().set("writeValue", uuid);
            Thread.sleep(15000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            writeLock.unlock();
            System.out.println("释放写锁 = " + Thread.currentThread().getId());
        }

        return uuid;
    }

    /**
     * 分布式读写锁
     * @return
     */
    @ResponseBody // 将方法返回的数据写入到响应体中
    @GetMapping({"/index/readValue"})
    public String testRedissonReadLock() {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");
        RLock readLock = readWriteLock.readLock();
        //加读锁
        readLock.lock();
        System.out.println("加读锁 = " + Thread.currentThread().getId());
        String writeValue = null;

        try {
            writeValue = redisTemplate.opsForValue().get("writeValue");
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readLock.unlock();
            System.out.println("释放读锁 = " + Thread.currentThread().getId());
        }

        return writeValue;
    }

    /**
     * redisson分布式信号量
     * 场景：停车,3个车位（手动在redis设置 key(park)的value（3））
     * 可以用信号量做分布式限流
     *
     *
     * @return
     */
    @ResponseBody // 将方法返回的数据写入到响应体中
    @GetMapping({"/index/park"})
    public String park() throws InterruptedException {
        RSemaphore park_semaphore = redissonClient.getSemaphore("park");
        //获取一个信号（这里可以理解成车位，获取一个值，占一个车位），阻塞方法，获取成功才会继续执行返回 ok
        // park_semaphore.acquire();
        //尝试获取一个信号，
        boolean b = park_semaphore.tryAcquire();
        if (b) {
            // todo 执行业务逻辑
        } else {
            return "流量过大，请稍等";
        }
        return "ok" + b;
    }

    @ResponseBody // 将方法返回的数据写入到响应体中
    @GetMapping({"/index/go"})
    public String go() throws InterruptedException {
        RSemaphore park_semaphore = redissonClient.getSemaphore("park");
        //释放一个信号（车位）
        park_semaphore.release();
        return "ok";
    }

    /**
     * redisson分布式闭锁
     * 场景：放假锁门，所有班（1到5班）的人都走完，锁大门
     *
     * @return
     * @throws InterruptedException
     */
    @ResponseBody // 将方法返回的数据写入到响应体中
    @GetMapping({"/index/lockDoor"})
    public String lockDoor() throws InterruptedException {
        RCountDownLatch countDownLatch = redissonClient.getCountDownLatch("lock-door");
        countDownLatch.trySetCount(5);//计数的总数
        countDownLatch.await();
        return "放假了，锁上大门。。。";
    }

    @ResponseBody // 将方法返回的数据写入到响应体中
    @GetMapping({"/index/gogogo/{id}"})
    public String gogogo(@PathVariable Long id) throws InterruptedException {
        RCountDownLatch countDownLatch = redissonClient.getCountDownLatch("lock-door");
        //计数减1
        countDownLatch.countDown();
        return String.format("【%s班】的学生走完了", id);
    }
}
